#include "component.hh"
#include "config.h"
#include "packet-in.hh"
#include "flow.hh"
#include "assert.hh"
#include "netinet++/ethernetaddr.hh"
#include "netinet++/ethernet.hh"
#include <xercesc/dom/DOM.hpp>
#include <boost/shared_array.hpp>
#include <boost/bind.hpp>
#ifdef LOG4CXX_ENABLED
#include <boost/format.hpp>
#include "log4cxx/logger.h"
#else
#include "vlog.hh"
#endif

#include "netinet++/ethernet.hh"
#include "netinet++/ip.hh"
#include "packets.h"
#include <time.h>
/*copied from switch.cc*/
#include "fnv_hash.hh"
#include "hash_set.hh"

using namespace std;
using namespace vigil;
using namespace vigil::container;

namespace
{
  static Vlog_module lg("logger");
  
  class SW_HostMac{
	public:
	datapathid switch_id;     /* Switch. */
	ethernetaddr host_mac;           /* Source MAC. */

	SW_HostMac(datapathid id, ethernetaddr mac){
		switch_id = id;
		host_mac = mac;
	}
	
	bool operator==(const SW_HostMac& swh2)
	{
		return switch_id == swh2.switch_id && host_mac == swh2.host_mac;
	}	
  };

  struct equal_SW_HostMac{
	bool operator()(const SW_HostMac& swh1, const SW_HostMac& swh2)const{
		return swh1.switch_id==swh2.switch_id && swh1.host_mac == swh2.host_mac;
	}
  };

  struct hash_SW_HostMac{
	std::size_t operator()(const SW_HostMac& swh)const{
		 uint32_t x;
                 x = vigil::fnv_hash(&swh.switch_id, sizeof swh.switch_id);
                 x = vigil::fnv_hash(swh.host_mac.octet, sizeof swh.host_mac.octet, x);
                 return x;
	}
  };
  class ENTRY{
      public:
	int seq;
	//uint8_t* buf;
	boost::shared_array<char> buf;
	int size;
	friend bool operator<(const ENTRY& a, const ENTRY& b){
		return (a.seq < b.seq);
	}
  };
  class FlowBuff{
	private:
		typedef list<ENTRY> ENTRY_LIST;
		typedef hash_map<uint64_t, ENTRY_LIST> TABLE;
		TABLE table;
	public:
		FlowBuff(){
		}

		void store_entry(uint8_t* new_packet, uint64_t flowid, int size, int seq){
			TABLE::iterator it = table.find(flowid);
			
			boost::shared_array<char> buf(new char[size]);
			uint8_t *tmp = (uint8_t*)buf.get();
			//entry.buf = (uint8_t*)buf;
			//printf("new_packet = %s\n", new_packet);
			memcpy(tmp, new_packet, size);
			//printf("buf = %s\n", buf.get());
			ENTRY entry;
			entry.buf = buf;
			entry.size = size;
			entry.seq = seq;
			if(it == table.end()){
				ENTRY_LIST newList;
				newList.push_back(entry);
				table[flowid] = newList;
			}else{
				it->second.push_back(entry);
				//it->second.sort();
			}

			return;
		}
		
		/*void store_entry(uint8_t* new_packet,  uint64_t flowid, int size){
			TABLE::iterator it = table.find(flowid);
			if(it == table.end()){ //new entry
				char * buf = new char[1500];
				ENTRY entry;
				entry.buf = (uint8_t*)buf;
 				entry.size = size;
				memcpy(entry.buf, new_packet, size);
				table[flowid] = entry;
			}else{
				memcpy(it->second.buf+it->second.size, new_packet, size);
				it->second.size += size;
			}			

			return;
		}*/

		void delete_entry(uint64_t flowid){
			TABLE::iterator it = table.find(flowid);
			if(it != table.end()){
				/*we can use boost::shared_ptr to avoid the following lines*/
				/*ENTRY_LIST::iterator lit = it->second.begin();
				while( lit != it->second.end()){
					delete lit->buf;
				}*/

				it->second.clear();
				table.erase(it);
			}
			return;
		}

		boost::shared_array<char> get_entry(uint64_t flowid, int &res_size){
			TABLE::iterator it = table.find(flowid);
			res_size = 0;
			if(it != table.end()) {
				ENTRY_LIST flist;
				flist = it->second;
				string tmp;
				ENTRY_LIST::iterator it = flist.begin();
				while(it != flist.end()){
					res_size += it->size;
					it++;
				}
				boost::shared_array<char> packet(new char[res_size]);
				it = flist.begin();
				char* result = packet.get();
				while(it != flist.end()){
					memcpy(result, it->buf.get(), it->size);
					result = result+it->size;
					it++;
				}
				return packet;
			}

			boost::shared_array<char> packet(NULL);
			return packet;
		}
  };
  /** Learning switch.
   */
  class Logger
    : public Component 
  {
  private: 
	/**
	Hash map between datapathid|macaddr, associated_port
	*/
	typedef hash_map<SW_HostMac, uint16_t, hash_SW_HostMac, equal_SW_HostMac> MacDB;
	MacDB mac_table;
	FlowBuff http_table;
  public:
    /** Constructor.
     */
    Logger(const Context* c, const xercesc::DOMNode*)
      : Component(c) 
    { }
    
    /** Configuration.
     * Add handler for packet-in event.
     */
    void configure(const Configuration*) 
    {
      register_handler<Packet_in_event>
	(boost::bind(&Logger::handle, this, _1));
    }
    
    /** Just simply install.
     */
    void install() 
    {
      lg.dbg(" Install called ");
    }

    /** Function to setup flow.
     */
    void setup_flow(Flow& flow, datapathid datapath_id , 
		    uint32_t buffer_id, uint16_t out_port)
    {
      ofp_flow_mod* ofm;
      size_t size = sizeof *ofm + sizeof(ofp_action_output);
      boost::shared_array<char> raw_of(new char[size]);
      ofm = (ofp_flow_mod*) raw_of.get();

      ofm->header.version = OFP_VERSION;
      ofm->header.type = OFPT_FLOW_MOD;
      ofm->header.length = htons(size);
      ofm->match.wildcards = htonl(0);
      ofm->match.in_port = htons(flow.in_port);
      ofm->match.dl_vlan = flow.dl_vlan;
      memcpy(ofm->match.dl_src, flow.dl_src.octet, sizeof ofm->match.dl_src);
      memcpy(ofm->match.dl_dst, flow.dl_dst.octet, sizeof ofm->match.dl_dst);
      ofm->match.dl_type = flow.dl_type;
      ofm->match.nw_src = flow.nw_src;
      ofm->match.nw_dst = flow.nw_dst;
      ofm->match.nw_proto = flow.nw_proto;
      ofm->match.tp_src = flow.tp_src;
      ofm->match.tp_dst = flow.tp_dst;
      ofm->command = htons(OFPFC_ADD);
      ofm->buffer_id = htonl(buffer_id);
      ofm->idle_timeout = htons(5);
      ofm->hard_timeout = htons(OFP_FLOW_PERMANENT);
      ofm->priority = htons(OFP_DEFAULT_PRIORITY);
      ofm->reserved = htonl(0);
      ofp_action_output& action = *((ofp_action_output*)ofm->actions);
      memset(&action, 0, sizeof(ofp_action_output));
      action.type = htons(OFPAT_OUTPUT);
      action.len = htons(sizeof(ofp_action_output));
      action.max_len = htons(0);
      action.port = htons(out_port);
      send_openflow_command(datapath_id, &ofm->header, true);
    }

    /** Function to handle packets.
     * @param datapath_id datapath id of switch
     * @param in_port port packet is received
     * @param buffer_id buffer id of packet
     * @param source source mac address in host order
     * @param destination destination mac address in host order
     */
    int handle_packet(datapathid datapath_id, uint16_t in_port, uint32_t buffer_id, 
		       uint64_t source, uint64_t destination)
    {
      int out_port = -1; 
      /*Learn the mapping between <datapathid|macaddr, port> */
	SW_HostMac src(datapath_id, source);
	mac_table[src] = in_port;

      /*lookup the map, sendout the packet*/
	SW_HostMac dst(datapath_id, destination);
	MacDB::iterator it = mac_table.find(dst);
	if( it != mac_table.end()) {
		out_port = it->second;
	}

	send_openflow_packet(datapath_id, buffer_id,
                                 out_port == -1 ? OFPP_FLOOD : out_port,
                                 in_port, true);
        return out_port;
      //send_openflow_packet(datapath_id, buffer_id, OFPP_FLOOD,
      //			   in_port, true);
    }
    
    int Find_Pattern(uint8_t* msg, uint8_t* pattern, int size_msg, int size_pattern) {
	//cout<<"find pattern - msg begin: "<<endl;
	int res = -1;
	for (int i=0; i<size_msg; i++) {
 	    int j;
	    for (j=0; j<size_pattern; j++) {
		if (msg[i+j] != pattern[j]) {
		    break;
		}
	    }
            if ( j == size_pattern) {
		res = i;
		return res;
	    }
/*	    
	    if(msg[i] >= 33 && msg[i] <= 126){
	    	printf("%c , ", msg[i]);
	    }else{
	    	printf("%d , ", msg[i]);
	    }
*/
	}
	//cout<<endl;
 	return res;
    }
    int Find_Cookie(uint8_t* msg, int msg_size, int &cookie_index, int &cookie_end_index, 
		int &header_end_index, char * cookie) {
	    
	    int pos_cookie = Find_Pattern(msg, (uint8_t*)cookie, 
			    msg_size, strlen(cookie));
	    char eol1[5]={13,10,13,10};
	    char eol2[3]={10,10};
	    int eol1_pos = Find_Pattern(msg, (uint8_t*)eol1, msg_size, 4);
	    int eol2_pos = Find_Pattern(msg, (uint8_t*)eol2, msg_size, 2);

	    char eoh1[3] = {13,10};
	    char eoh2[2] = {10};
	    int eoh1_pos = Find_Pattern(msg+pos_cookie+1, (uint8_t*)eoh1, msg_size - pos_cookie, 2);
	    int eoh2_pos = Find_Pattern(msg+pos_cookie+1, (uint8_t*)eoh2, msg_size - pos_cookie, 1);

	    header_end_index = eol1_pos;
	    if (eol2_pos != -1)
		    header_end_index = eol2_pos;

	    cookie_index = pos_cookie;

	    cookie_end_index = -1;
	    if (eoh1_pos != -1)
	    	   cookie_end_index = eoh1_pos + pos_cookie;
	    if (eoh2_pos != -1)
		   cookie_end_index = eoh2_pos + pos_cookie;
		    
	   return 0;
    }
    
    boost::shared_array<char> mapServiceName(uint16_t port){
	    const int NAMESIZE = 1000;
	    boost::shared_array<char> service_name(new char[NAMESIZE]);
	    char command[200] = "\0";
	    snprintf(command, sizeof command, 
			    "awk \'$2 ~ /^%d\\/tcp/ {print $1}\' /etc/services > /home/openflow/tmp", port);
	    //printf("cmd = %s\n", command);
	    system(command);
	    FILE* tmp = fopen("/home/openflow/tmp","r");
	    char *buf = service_name.get();
	    memset(buf,'\0', NAMESIZE);
	    fscanf(tmp,"%s", buf);
	    //printf("buf = %s\n",buf);
	    if(strlen(buf) == 0 ){
		    snprintf(buf, NAMESIZE, "%d", port);
	    }
	    system("rm /home/openflow/tmp");
	    return service_name;
    }
    void learning_switch(const Packet_in_event& pi, bool install){
	uint32_t buffer_id = pi.buffer_id;
	Flow flow(pi.in_port, *pi.get_buffer());
	if( install ){
		if (!flow.dl_src.is_multicast()){
			int out_port = -1;
			out_port = handle_packet(pi.datapath_id, pi.in_port, buffer_id, 
					flow.dl_src.hb_long(), flow.dl_dst.hb_long());
			if( out_port != -1){ //setup flow
				setup_flow(flow, pi.datapath_id, buffer_id, out_port);
			}
		}else
			send_openflow_packet(pi.datapath_id, buffer_id, OFPP_FLOOD,
					pi.in_port, true);
	}else{
		send_openflow_packet(pi.datapath_id, buffer_id, OFPP_FLOOD,
                                        pi.in_port, true);
	}
    }
    /** Packet-on handler.
     */
    Disposition handle(const Event& e)
    {
      const Packet_in_event& pi = assert_cast<const Packet_in_event&>(e);
      uint32_t buffer_id = pi.buffer_id;
      
      Buffer* b = pi.get_buffer().get();
      
      Flow flow(pi.in_port, *pi.get_buffer());
      char buffer[2000];
      time_t timep;
      time (&timep);
      string logmsg("");
      bool install_flow = false;
      // drop LLDP packets
      if (flow.dl_type == ethernet::LLDP)
        return CONTINUE;
      //printf("port: %"PRIu16"\n", ntohs(flow.tp_dst));
      // check whether it is TCP packets
      if (flow.nw_proto ==  ip_::proto::TCP){
		//logging
		snprintf(buffer, sizeof buffer, "%s", asctime(gmtime(&timep)));
		buffer[strlen(buffer) - 1] = '\0';
		buffer[strlen(buffer)] = '\0';
		logmsg += string(buffer);
		snprintf(buffer, sizeof buffer,
				" [%u.%u.%u.%u] -> [%u.%u.%u.%u] : ",
				((unsigned char *)&flow.nw_src)[0],
				((unsigned char *)&flow.nw_src)[1],
				((unsigned char *)&flow.nw_src)[2],
				((unsigned char *)&flow.nw_src)[3],
				((unsigned char *)&flow.nw_dst)[0],
				((unsigned char *)&flow.nw_dst)[1],
				((unsigned char *)&flow.nw_dst)[2],
				((unsigned char *)&flow.nw_dst)[3]);

		logmsg += string(buffer);

		
		if( flow.tp_src == htons(80) || flow.tp_dst == htons(80) ||
			flow.tp_src == htons(8080) || flow.tp_dst == htons(8080) ||
			flow.tp_src == htons(8008) || flow.tp_dst == htons(8008) ){ //HTTP

			//printf("port is %d -> %d and we get a http\n",ntohs(flow.tp_src), ntohs(flow.tp_dst));
			if(flow.tp_src == htons(80)|| flow.tp_src == htons(8080) || flow.tp_src == htons(8008)){
				snprintf(buffer, sizeof buffer, "http -> %d", ntohs(flow.tp_dst));
			}else{
				snprintf(buffer, sizeof buffer, "%d -> http", ntohs(flow.tp_src));
			}
			//printf("buf = %s\n", buffer);
			logmsg += string(buffer);
			snprintf(buffer, sizeof buffer, " (%"PRIu64")", flow.hash_code());
			logmsg += string(buffer);
			//int pos = Find_Pattern(b->data(), (uint8_t*)cookie, b->size(), 6);
			uint8_t payload_pos = sizeof(eth_header) + sizeof(ip_header) + sizeof(tcp_header);
			uint8_t* payload_buff = b->data()+payload_pos;
			const tcp_header *tcp = b->try_at<tcp_header>(0);
			//const tcp_header *tcp = 0;
			int existPkt_size;
			//printf("before get_entry\n");
			boost::shared_array<char> existPktPtr=http_table.get_entry(flow.hash_code(),  existPkt_size);
			uint8_t* existPkt = (uint8_t*)existPktPtr.get();
			int added = 0;
			if( existPkt != NULL){
			//	printf("existPkt = %s\n", (char*)existPkt);
				http_table.store_entry( payload_buff, flow.hash_code(), b->size() - payload_pos, 
								tcp->tcp_seq);
				added = 1;
				existPktPtr = http_table.get_entry(flow.hash_code(), existPkt_size);
				existPkt = (uint8_t*)existPktPtr.get();
			} else {
			//	printf("existPkt is NULL\n");
				existPkt = payload_buff;
				existPkt_size = b->size() - payload_pos;
			//	printf("existPkt = %s\n", (char*)existPkt);
			}
			//printf("before Find_cookie\n");
			int cookie_start_index, cookie_end_index, header_end_index;
			int cookie_start_index2, cookie_end_index2, header_end_index2;

			char c1[10] = "Cookie:";
			char c2[10] = "Cookie2:";

			Find_Cookie(existPkt, existPkt_size, cookie_start_index, 
				cookie_end_index, header_end_index, c1);
			Find_Cookie(existPkt, existPkt_size, cookie_start_index2, 
				cookie_end_index2, header_end_index2, c2);
//cout << cookie_start_index << " " << cookie_end_index << " " << header_end_index << endl;
//cout << cookie_start_index2 << " " << cookie_end_index2 << " " << header_end_index2 << endl;
			int hasColon = 0;
			if (header_end_index != -1) {
				//the incoming packet + buffer has everything
				if (cookie_start_index != -1 && cookie_end_index != -1) {
					memcpy(buffer, existPkt+cookie_start_index, cookie_end_index - 
						cookie_start_index + 1);
					buffer[cookie_end_index - cookie_start_index] = '\0';
					logmsg += " : "+string(buffer);
					hasColon = 1;
				}
                                if (cookie_start_index2 != -1 && cookie_end_index2 != -1) {
                                        memcpy(buffer, existPkt+cookie_start_index2, cookie_end_index2 - 
                                                cookie_start_index2 + 1);
                                        buffer[cookie_end_index2 - cookie_start_index2] = '\0';
					if (hasColon == 0) 
						logmsg += " :";
                                        logmsg += " "+string(buffer);   
                                }
				http_table.delete_entry(flow.hash_code() );
				install_flow = true;
			}else if (header_end_index ==  -1) {
				// put into table if necessary
				if (added == 0) {
					http_table.store_entry( payload_buff, flow.hash_code(), b->size() - payload_pos,
							tcp->tcp_seq);
				}
				install_flow = false;
			} 
			
			learning_switch(pi, install_flow);

		}else{ 
			//tcp, but not http
			//printf("tcp, but not http!\n");
			install_flow = true;		
			learning_switch(pi, install_flow);
			// pass handle of unicast packet, else flood
			//to do: map port # -> service name
			boost::shared_array<char> srcSN = mapServiceName(ntohs(flow.tp_src));
			boost::shared_array<char> dstSN = mapServiceName(ntohs(flow.tp_dst));
			//snprintf(buffer, sizeof buffer, "%d -> %d", ntohs(flow.tp_src),ntohs(flow.tp_dst));
			snprintf(buffer, sizeof buffer, "%s -> %s", srcSN.get(), dstSN.get());
			logmsg += string(buffer);
			snprintf(buffer, sizeof buffer, " (%"PRIu64")", flow.hash_code());
			logmsg += string(buffer);
		}
		if (install_flow) {
			cout<<logmsg<<endl;
			lg.dbg("%s",logmsg.c_str());
		}
      }else{
		//non-tcp
	      install_flow = true;		
	      learning_switch(pi, install_flow);
       }
      //printf("finish handle_packet\n");
      return CONTINUE;
    }
    
  private:
};

REGISTER_COMPONENT(container::Simple_component_factory<Logger>,
                   Logger);

} // unnamed namespace
